/* Copyright (c) 2007  Peter Palotas
 *  
 *  This software is provided 'as-is', without any express or implied
 *  warranty. In no event will the authors be held liable for any damages
 *  arising from the use of this software.
 *  
 *  Permission is granted to anyone to use this software for any purpose,
 *  including commercial applications, and to alter it and redistribute it
 *  freely, subject to the following restrictions:
 *  
 *      1. The origin of this software must not be misrepresented; you must not
 *      claim that you wrote the original software. If you use this software
 *      in a product, an acknowledgment in the product documentation would be
 *      appreciated but is not required.
 *  
 *      2. Altered source versions must be plainly marked as such, and must not be
 *      misrepresented as being the original software.
 *  
 *      3. This notice may not be removed or altered from any source
 *      distribution.
 *  
 *  
 *  $Id: MultiHashDictionary.cs 19 2007-08-15 13:14:32Z palotas $
 */
using System;
using SCG = System.Collections.Generic;
using C5;

namespace Plossum.Collections
{
    /// <summary>
    /// Represents a collection of keys and values where each key may occur multiple times in the collection.
    /// </summary>
    /// <typeparam name="TKey">The type of the keys in the dictionary.</typeparam>
    /// <typeparam name="TValue">The type of the values in the dictionary.</typeparam>
    /// <remarks> 
    /// <para>This is implemented as a <see cref="T:C5.HashDictionary`2"/> where the value type is an 
    /// <see cref="T:System.Collections.Generic.ICollection`2"/>. The default constructor supplies a 
    /// default factory that uses <see cref="T:C5.HashSet`1"/> for storing value types. This means that the
    /// key/value pair must be unique in the collection. You can also specify another factory to use a different
    /// collection for storing values.</para>
    /// <para>This implementation is based on the implementation suggested in secion 11.2 of "The 
    /// C5 Generic Collection Library for C# and CLI Version 1.0.1 of 2006-06-26" by Niels Kokholm and Peter Sestoft.</para>
    /// </remarks>
    [Serializable]
    public class MultiHashDictionary<TKey, TValue> : HashDictionary<TKey, ICollection<TValue>> , IMultiDictionary<TKey, TValue>
    {
        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="T:MultiHashDictionary`2"/> class that provides a default
        /// value collection factory of type <see cref="T:C5.HashSet`1"/> to store the values.
        /// </summary>
        /// <note>This means that they value collection contains only unique values, meaning that there can be no two
        /// identical key/value pairs in this container. <see cref="M:MultiHashDictionary`2#ctor(Fun`1)"/>
        /// for a way to provide another value collection type.</note>
        public MultiHashDictionary()
            : this(delegate() { return new HashSet<TValue>(); })
        {            
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="T:MultiHashDictionary`2"/> class.
        /// </summary>
        /// <param name="valueCollectionFactory">The value collection factory.</param>
        /// <remarks>This constructor allows you to provide a collection factory for creating 
        /// the collection that will hold the values for each key.  The type of the value collection
        /// should provide a fast <b>Contains</b> method, so types such as <see cref="T:C5.LinkedList`1"/>
        /// are normally not desireable.  Consider using a <see cref="T:C5.HashBag"/> if you want 
        /// to allow any number of the same key/value combination in this <see cref="T:MultiHashDictionary`2"/>.</remarks>
        public MultiHashDictionary(Fun<ICollection<TValue>> valueCollectionFactory)
        {
            mVcFactory = valueCollectionFactory;

            ItemsAdded +=
                delegate(Object sender, ItemCountEventArgs<KeyValuePair<TKey, ICollection<TValue>>> args)
                {
                    ICollection<TValue> values = args.Item.Value;
                    if (values != null)
                    {
                        mCount += values.Count;
                        values.ItemsAdded += IncrementCount;
                        values.ItemsRemoved += DecrementCount;
                        values.CollectionCleared += ClearedCount;
                    }
                };

            ItemsRemoved +=
                delegate(Object sender, ItemCountEventArgs<KeyValuePair<TKey, ICollection<TValue>>> args)
                {
                    ICollection<TValue> values = args.Item.Value;
                    if (values != null)
                    {
                        mCount -= values.Count;
                        values.ItemsAdded -= IncrementCount;
                        values.ItemsRemoved -= DecrementCount;
                        values.CollectionCleared -= ClearedCount;
                    }
                };
        }

        #endregion

        #region Public properties

        /// <summary>
        /// Gets the number of key/value pairs contained in the <see cref="T:MultiHashDictionary`2"/>. 
        /// </summary>
        /// <value>the number of key/value pairs contained in the <see cref="T:MultiHashDictionary`2"/>.</value>
        /// <remarks>Getting the value of this property is an O(1) operation.</remarks>
        public override int Count
        {
            get
            {
                return mCount;
            }
        }

        /// <summary>
        /// Gets the number of unique keys contained in the <see cref="T:MultiHashDictionary`2"/>. 
        /// </summary>
        /// <value>the number of unique keys contained in the <see cref="T:MultiHashDictionary`2"/>.</value>
        public int UniqueKeyCount
        {
            get
            {
                return base.Count;
            }
        }

        #endregion

        #region Public methods

        /// <summary>
        /// Adds the specified key and value to the dictionary. 
        /// </summary>
        /// <param name="key">The key of the element to add.</param>
        /// <param name="value">The value of the element to add.</param>
        /// <exception cref="ArgumentNullException"><paramref name="key"/> is a null reference (<b>Nothing</b> in Visual Basic), 
        /// or, the value collection does not support storing null references.</exception>
        /// <exception cref="ArgumentException">An identical key/value element already exists in the collection if the 
        /// value collection does not support duplicates.</exception>
        public virtual void Add(TKey key, TValue value)
        {
            ICollection<TValue> values;
            if (!base.Find(key, out values) || values == null)
            {
                values = mVcFactory();
                Add(key, values);
            }
            values.Add(value);
        }

        /// <summary>
        /// Removes all elements with the specified <paramref name="key"/> and <paramref name="value"/> from this <see cref="T:MultiHashDictionary`2"/>.
        /// </summary>
        /// <param name="key">The key of the element to remove</param>
        /// <param name="value">The value of the element to remove.</param>
        /// <returns><b>true</b> if the element(s) are successfully removed; otherwise <b>false</b>.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="key"/> is a null reference (<b>Nothing</b> in Visual Basic)</exception>
        public virtual bool Remove(TKey key, TValue value)
        {
            ICollection<TValue> values;
            if (base.Find(key, out values) && values != null)
            {
                if (values.Remove(value))
                {
                    if (values.IsEmpty)
                        base.Remove(key);
                    return true;
                }
            }
            return false;
        }

        /// <summary>
        /// Determines whether this <b>MultiHashDictionary</b> contains the specified <paramref name="key"/>.
        /// </summary>
        /// <param name="key">The key to search for</param>
        /// <returns>
        /// 	<c>true</c> if this <see cref="T:MultiHashDictionary`2"/> contains at least one element with 
        /// the specified <paramref name="key"/>; otherwise, <c>false</c>.
        /// </returns>
        public override bool Contains(TKey key)
        {
            ICollection<TValue> values;
            return base.Find(key, out values) && values != null && !values.IsEmpty;
        }

        /// <summary>
        /// Returns the number of elements in the <b>MultiHashDictionary</b> with the specified <paramref name="key"/>.
        /// </summary>
        /// <param name="key">The key to search for.</param>
        /// <returns>
        /// the number of elements in the <see cref="T:IMultiDictionary`2"/> with the specified <paramref name="key"/>.
        /// </returns>
        public int KeyCount(TKey key)
        {
            ICollection<TValue> values;
            return base.Find(key, out values) && values != null ? values.Count : 0;
        }

        /// <summary>
        /// Gets or sets the <see cref="T:C5.ICollection`1"/> associated with the specified key.
        /// </summary>
        /// <value>The value associated with the specified key. If the specified key is not found, a get operation 
        /// throws a <see cref="System.Collections.Generic.KeyNotFoundException" />, and a set operation creates a 
        /// new element with the specified key. </value>
        /// <param name="key">The key of the value to get or set.</param>
        public override ICollection<TValue> this[TKey key]
        {
            get
            {
                
                ICollection<TValue> values;
                return base.Find(key, out values) && values != null ? values : mVcFactory();
            }

            set
            {
                base[key] = value;
            }
        }

        /// <summary>
        /// Removes all keys and values from the <see cref="T:MultiHashDictionary`2"/>. 
        /// </summary>
        public override void Clear()
        {
            foreach (ICollection<TValue> values in Values)
            {
                if (values != null)
                {
                    mCount -= values.Count;
                    values.ItemsAdded -= IncrementCount;
                    values.ItemsRemoved -= DecrementCount;
                    values.CollectionCleared -= ClearedCount;
                }
            }
            base.Clear();
        }

        #endregion

        #region Private methods

        /// <summary>
        /// Event listener used for incrementing the counter keeping track of the number of elements in this collection.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="args">The <see cref="T:C5.ItemCountEventArgs`2"/> instance containing the event data.</param>
        private void IncrementCount(Object sender, ItemCountEventArgs<TValue> args)
        {
            mCount += args.Count;
        }

        /// <summary>
        /// Event listener used for decrementing the counter keeping track of the number of elements in this collection.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="args">The <see cref="T:C5.ItemCountEventArgs`2"/> instance containing the event data.</param>
        private void DecrementCount(Object sender, ItemCountEventArgs<TValue> args)
        {
            mCount -= args.Count;
        }

        /// <summary>
        /// Event listener used for decrementing the counter keeping track of the number of elements in this collection.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="args">The <see cref="T:C5.ItemCountEventArgs`2"/> instance containing the event data.</param>
        private void ClearedCount(Object sender, ClearedEventArgs args)
        {
            mCount -= args.Count;
        }

        #endregion 

        #region Private fields

        private int mCount = 0;
        private readonly Fun<ICollection<TValue>> mVcFactory;

        #endregion
    }
}
